﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;

using System.Windows.Threading;
using System.Xml;
using System.Text.RegularExpressions;
using System.Windows.Controls.Primitives;

using System.IO;

using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Document;
using Microsoft.Win32;

using CryForLua_wpf_AvEd.UserPreferences;
using CryForLua_wpf_AvEd.CodeCompletion;
using CryForLua_wpf_AvEd.CodeAnalysing;

using System.Threading;


using System.Timers;



namespace CryForLua_wpf_AvEd
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        // Mid mouse button scrolling

        private enum eMidMouseScrollDir
        {
            eMMSD_None,
            eMMSD_Up,
            eMMSD_Down,
        };
        private bool m_bIsScrollingMidMouse = false;
        private Point m_v2LastCursorPos = new Point(0, 0);
        private eMidMouseScrollDir m_scrollDir;
        private float m_fScrollSpeed = 1.0f;
        private DispatcherTimer m_scrollTimer = null;


        CompletionWindow completionWindow;

        FoldingManager foldingManager;
        AbstractFoldingStrategy foldingStrategy;
       
        private string[] cmdArgs;
        private string currentFilePath = "";
        private string luaCompilerPath = "";
        private bool bTextChangedSinceLastSave = false;
        private Indentation ind = new Indentation();

        private int m_numChanges = 0;
        private LuaDocument m_doc;

        /// <summary>
        /// Indicates whether the completion window is showing.
        /// </summary>
        public bool IsShowingCompletionWindow {get; private set;}
       
        /// <summary>
        /// Indicates whether the completion window shows the global table.
        /// </summary>
        public static bool IsCompletionWindowShowingGlobals {get; set;}

        private static DispatcherTimer m_statusBarResetTimer = new DispatcherTimer();

        private static string StatusBarMessage { get; set; }

        private static bool m_bStatusBarReset = false;

        private static TimeSpan m_onemsTimeSpan = new TimeSpan(0, 0, 0, 0, 1);
        private static TimeSpan m_twosecTimeSpan = new TimeSpan(0, 0, 0, 2);

        private Thread analThread = new Thread(new ThreadStart(CodeAnalyser.ParseStatic));
        private Thread foldThread = null;


        public MainWindow()
        {
            Stream manifestResourceStream = Application.ResourceAssembly.GetManifestResourceStream("CryForLua_wpf_AvEd.LuaSyntaxDefinition.xshd");
            IHighlightingDefinition luaHighlightDef;
            using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("CryForLua_wpf_AvEd.LuaSyntaxDefinition.xshd"))
            {
                if (manifestResourceStream == null)
                    throw new InvalidOperationException("Could not find embedded resource");
                using (XmlReader reader = new XmlTextReader(manifestResourceStream))
                {
                    luaHighlightDef = ICSharpCode.AvalonEdit.Highlighting.Xshd.HighlightingLoader.Load(reader, HighlightingManager.Instance);
                }
            }

            
            HighlightingManager.Instance.RegisterHighlighting("LuaSyntaxDefinition", new string[] { ".cool" }, luaHighlightDef);
            InitializeComponent();

            CodeTable.Instance.ReadGlobalTableFromFile();
            UserPreferencesData.Instance.ReadUserSettingsFromFile();

            luaCompilerPath = UserPreferencesData.Instance.cryEngineRootDir + "\\Bin32\\LuaCompiler.exe";

            textEditor.TextArea.TextEntering += TextEditorOnTextEntering;
            textEditor.TextArea.TextEntered += TextEditorOnTextEntered;



            foldingStrategy = new LuaFolding();
            foldingManager = FoldingManager.Install(this.textEditor.TextArea);

            cmdArgs = Environment.GetCommandLineArgs();

            if (cmdArgs.Length > 1)
            {
                LoadFile(cmdArgs[1]);
            }

            textEditor.TextArea.IndentationStrategy = ind;
           


            IsShowingCompletionWindow = false;
            IsCompletionWindowShowingGlobals = false;

            m_scrollTimer = new DispatcherTimer();
            m_scrollTimer.Interval = new TimeSpan(0, 0, 0, 0, 30);
            m_scrollTimer.Tick += scrollTimer_Tick;

           
            m_statusBarResetTimer.Tick += SetStatusBarMessageFromTimer;

            foldThread = new Thread(new ThreadStart(this.UpdateFoldings));

          
        }

       

        

       

        #region Texteditor

        private void TextEditorOnTextEntering(object sender, TextCompositionEventArgs e)
        {
            bTextChangedSinceLastSave = true;

            /*if (foldThread != null)
            {
                if (foldThread.ThreadState != System.Threading.ThreadState.Running)
                {
                    foldThread = new Thread(new ThreadStart(this.UpdateFoldings));
                    foldThread.Start();
                }
            }*/

            UpdateFoldings();
            UpdateNavigationDropDown();
        }

        private void UpdateFoldings()
        {
            foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);
        }
        private void TextEditorOnTextEntered(object sender, TextCompositionEventArgs e)
        {
            // Close the window if it's empty
            if (completionWindow != null)
            {
                if (completionWindow.CompletionList.ListBox.HasItems == false)
                {
                    completionWindow.Close();
                }
            }

            CodeAnalyser.s_codeText = textEditor.Text;

            Stopwatch watch = new Stopwatch();

            watch.Restart();
            m_doc = CodeAnalyser.ParseScript(textEditor.Text);
            ShowStatusBarMessage(watch.ElapsedTicks.ToString());
            
           // UpdateNavigationDropDown();


            if (e.Text == ":" || e.Text == ".")
            {
                // Open code completion for functions after the user has pressed colon:
                completionWindow = new CompletionWindow(textEditor.TextArea);

                // Get a possible classname (meaning the text before the . or :

                char[] pWhitespaces = { ' ', '\0', '\n', '\t'};

                int possibleClassNameStart = textEditor.Text.LastIndexOfAny(pWhitespaces, textEditor.TextArea.Caret.Offset - 1);
                int startOffset = 1;

                if (possibleClassNameStart == -1)
                {
                    if (textEditor.LineCount < 2)
                    {
                        possibleClassNameStart = 0;
                        startOffset = 0;
                    }
                }

                int finalPosition = possibleClassNameStart + startOffset;
                int length = textEditor.TextArea.Caret.Offset - finalPosition - 1;
                string possibleClassname = textEditor.Text.Substring(finalPosition, length);

                IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;

                // If it's a global table like AI etc. only  show functions for that table
                if (CodeTable.TABLE.ContainsKey(possibleClassname))
                {
                    foreach (CodeTable.LuaFunction function in CodeTable.GetFunctionTableByClassName(possibleClassname).classFunctions)
                    {
                        data.Add(new CompletionData(function.name, function.description, function.paramList));
                    }
                }
                else
                {
                    // Else it's mostlikely inside an entity, so show all available entity functions

                    foreach (CodeTable.LuaFunction function in CodeTable.GetFunctionTableByClassName("Entity").classFunctions)
                    {
                        data.Add(new CompletionData(function.name, function.description, function.paramList));
                    }

                }

                completionWindow.Show();
                IsShowingCompletionWindow = true;
                IsCompletionWindowShowingGlobals = false;  // Prevents mix ups of both cases

                completionWindow.Closed += delegate
                {
                    completionWindow = null;
                    IsShowingCompletionWindow = false;
                    // Do not set IsCompletionWindowShowingGlobals to false here
                    // It's handled by Complete() inside CompletionData
                };

                
            }
            else
            {
                if (IsShowingCompletionWindow)
                    return;
                // There might be some global functions called...
              CodeTable.LuaFunctionTableBlah globalScope = CodeTable.GetFunctionTableByClassName("Global_scope");


                        
               Regex word = new Regex(@"\w+", RegexOptions.RightToLeft);

               Match wordMatch = word.Match(textEditor.Text);

                // If the match isn't the word that's currently typed do nothing
               if (wordMatch.Value == "" || wordMatch.Index + wordMatch.Length < textEditor.SelectionStart) return;

                foreach (CodeTable.LuaFunction function in globalScope.classFunctions)
                {
                    if (function.name.Contains(wordMatch.Value))
                    {
                        completionWindow = new CompletionWindow(textEditor.TextArea);

                        IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;

                        foreach (CodeTable.LuaFunction func in globalScope.classFunctions)
                        {
                            data.Add(new CompletionData(func.name, func.description, func.paramList));
                        }

                        completionWindow.Show();
                        IsShowingCompletionWindow = true;
                        IsCompletionWindowShowingGlobals = true;

                       

                        completionWindow.Closed += delegate
                        {
                            IsShowingCompletionWindow = false;
                            // Do not set IsCompletionWindowShowingGlobals to false here
                            // It's handled by Complete() inside CompletionData
                            completionWindow = null;
                        };
               
                    }
                }
            }
        }

        #endregion

        private void OnCollapseAllFoldingsClicked(object sender, RoutedEventArgs e)
        {
            foreach (FoldingSection section in foldingManager.AllFoldings)
            {
                section.IsFolded = true;
            }
        }



        private void OnExpandAllFoldingsClicked(object sender, RoutedEventArgs e)
        {
            foreach (FoldingSection section in foldingManager.AllFoldings)
            {
                section.IsFolded = false;
            }
        }

        #region Middle mouse button scrolling

        private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Pressed)
            {
                    m_bIsScrollingMidMouse = true;
                    m_v2LastCursorPos = e.GetPosition(textEditor);
                    m_scrollTimer.Start();

                    textEditor.TextArea.Cursor = Cursors.ScrollNS;
            }
           

        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (m_bIsScrollingMidMouse)
            {
                Point pos = e.GetPosition(textEditor);

                int threshold = 10;

                if (pos.Y > m_v2LastCursorPos.Y + threshold)
                {
                    m_scrollDir = eMidMouseScrollDir.eMMSD_Down;
                  
                }
                else if (pos.Y < m_v2LastCursorPos.Y - threshold)
                {
                    m_scrollDir = eMidMouseScrollDir.eMMSD_Up;
                }
                else
                {
                    m_scrollDir = eMidMouseScrollDir.eMMSD_None;
                }

                m_fScrollSpeed = (float)Math.Abs(m_v2LastCursorPos.Y - pos.Y);
                m_fScrollSpeed /= 10;
                m_fScrollSpeed *= m_fScrollSpeed;
             
                
            }
        }

       

        void scrollTimer_Tick(object sender, EventArgs e)
        {
            if (m_bIsScrollingMidMouse)
            {
                switch (m_scrollDir)
                {
                    case eMidMouseScrollDir.eMMSD_Up:
                        {
                            textEditor.ScrollToVerticalOffset(textEditor.VerticalOffset - m_fScrollSpeed);
                        } break;
                    case eMidMouseScrollDir.eMMSD_Down:
                        {
                            textEditor.ScrollToVerticalOffset(textEditor.VerticalOffset + m_fScrollSpeed);
                        } break;
                    default:
                        {

                        } break;
                }
            }
        }

        private void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Released)
            {
                m_bIsScrollingMidMouse = false;
                m_v2LastCursorPos = e.GetPosition(textEditor);
                m_scrollTimer.Stop();

                textEditor.TextArea.Cursor = Cursors.Arrow;
            }
        }

        #endregion

        private void OnOpenUserPreferencesClicked(object sender, RoutedEventArgs e)
        {
            UserPreferencesWindow window = new UserPreferencesWindow();

            window.Show();
        }

        private void OnSearchForScriptbindsClicked(object sender, RoutedEventArgs e)
        {
            CodeCompletion.AutoCompletitionManager manager = new AutoCompletitionManager();

            manager.SearchDirectories();

        }

        private void OnEditScriptbindsClicked(object sender, RoutedEventArgs e)
        {
            ScriptBindLibWin window = new ScriptBindLibWin();

            // Show dialog makes sure nothing goes wrong when closing the app
            window.ShowDialog();
        }

        #region File handling

        private void LoadFile(string sFilepath)
        {
            if (sFilepath != "")
            {
                textEditor.Load(sFilepath);
                currentFilePath = sFilepath;
                foldingStrategy.UpdateFoldings(foldingManager, this.textEditor.Document);
                UpdateWindowTitle(sFilepath.Substring(sFilepath.LastIndexOf("\\") + 1, sFilepath.Length - sFilepath.LastIndexOf("\\")-1));
                m_doc = CodeAnalyser.ParseScript(textEditor.Text);
                UpdateNavigationDropDown();
            }
        }

        private void UpdateWindowTitle(string sFilename)
        {
            this.Title = sFilename + " - Cry For LUA";
        }

        private void OpenFileDialog(object sender, ExecutedRoutedEventArgs e)
        {
            OpenFileDialog openDialog = new OpenFileDialog();

            openDialog.CheckFileExists = true;

            openDialog.Filter = "(*.lua)|*.lua";  // (<Shown filter text)|<Actual filter>

            openDialog.ShowDialog();

            if (openDialog.FileName != "" )
            {
                LoadFile(openDialog.FileName);
            }
        }

        private void SaveFileAs(object sender, ExecutedRoutedEventArgs e)
        {
            SaveFileDialog dialog = new SaveFileDialog();

            Nullable<bool> result = dialog.ShowDialog();


            if (dialog.FileName != "" && result != null)
            {
                textEditor.Save(dialog.FileName);
                currentFilePath = dialog.FileName;
                UpdateWindowTitle(dialog.SafeFileName);
                ParseCurrentScript();
                bTextChangedSinceLastSave = false;
            }
        }

        private void CreateNewFile()
        {
            SaveFileDialog dialog = new SaveFileDialog();
            Nullable<bool> res = dialog.ShowDialog();

            if (res != false)
            {
               
                if (textEditor.Text != "")
                    File.WriteAllText(dialog.FileName, textEditor.Text);
                else
                    File.WriteAllText(dialog.FileName, "--"); // only writing some actual text seems to disable BOM pffft

               LoadFile(dialog.FileName);
               bTextChangedSinceLastSave = false;
            }
        }

        private void SaveFile(object sender, ExecutedRoutedEventArgs e, bool parse = true)
        {
            if (currentFilePath != "")
            {
                textEditor.Save(currentFilePath);
                if (parse)
                    ParseCurrentScript();
                bTextChangedSinceLastSave = false;
            }
            else
                CreateNewFile();

           
            ShowStatusBarMessage("File saved! ( " + currentFilePath + " )");
        }

        private void SaveFile(object sender, ExecutedRoutedEventArgs e)
        {
            SaveFile(sender, e, true);
        }

        #endregion

        #region Script parsing

        private void OnParseClicked(object sender, RoutedEventArgs e)
        {
            ParseCurrentScript();
        }

        private void ParseCurrentScript()
        {
            SaveFile(null,null, false);
            bTextChangedSinceLastSave = false;
           
            if (luaCompilerPath != "" && File.Exists(luaCompilerPath))
            {

                errorOutput.Clear();
                errorOutput.Text += "Parsing started...\n";

                Process LuaCompiler = new Process();
                StreamReader StdOut;

                ProcessStartInfo startInfo = new ProcessStartInfo(luaCompilerPath);

                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardOutput = true;
                startInfo.RedirectStandardError = true;
                startInfo.CreateNoWindow = true;
                startInfo.Arguments = "-p " + '"' + currentFilePath + '"';

                LuaCompiler.StartInfo = startInfo;
                LuaCompiler.Start();

                StdOut = LuaCompiler.StandardOutput;

                errorOutput.Text += StdOut.ReadToEnd();

                StdOut = LuaCompiler.StandardError;

                errorOutput.Text += StdOut.ReadToEnd();

                errorOutput.Text += "Compiler finished parsing with exitcode: " + LuaCompiler.ExitCode;

                textEditor.Focus();

                StdOut.Close();
            }
            else
            {
                MessageBox.Show("LuaCompiler.exe could't be found. Please run the CryEngine Settings Manager, specify a correct root path and restart Cry for lua!" +
                    " Don't worry your changes have been saved!",
                    "Parsing error",
                    MessageBoxButton.OK);
            }
        }

        #endregion

        private void OnAppClosing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (bTextChangedSinceLastSave)
            {
                MessageBoxResult res = MessageBox.Show("Changes not saved! Save now?", "Warning", MessageBoxButton.YesNoCancel);

                switch (res)
                {
                    case MessageBoxResult.Yes:
                            SaveFile(null, null);
                        break;
                    case MessageBoxResult.No:
                        break;
                    case MessageBoxResult.None:
                    case MessageBoxResult.Cancel:
                    default:
                        {
                            e.Cancel = true;
                        }break;
                }
            }
        }

        private void QuitApplication(object sender, ExecutedRoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }

       

      

        private void SetStatusBarMessageFromTimer(object sender, EventArgs e)
        {

            StatusBarItem item = statusBar1.Items[0] as StatusBarItem;

            if (m_bStatusBarReset)
            {
                m_statusBarResetTimer.Stop();
               

                item.Content = "Ready!";
                m_bStatusBarReset = false;
            }
            else
            {
                m_statusBarResetTimer.Interval = m_twosecTimeSpan;
                item.Content = StatusBarMessage;
                m_bStatusBarReset = true;
            }
           
        }

        /// <summary>
        /// Shows the string for two seconds in the main window's status bar.
        /// </summary>
        /// <param name="sMessage">This will be displayed for two seconds</param>
        public static void ShowStatusBarMessage(string sMessage)
        {
            StatusBarMessage = sMessage;
            m_bStatusBarReset = false;
            m_statusBarResetTimer.Interval = m_onemsTimeSpan;
            m_statusBarResetTimer.Start();
        }

        public void UpdateNavigationDropDown()
        {
            navigationDropdown.Items.Clear();

            if (m_doc.functions == null)
                return;

            foreach (CodeTable.LuaFunction func in m_doc.functions)
            {
                ComboBoxItem item = new ComboBoxItem();
                item.Content = func.name;
                item.ToolTip = func.description;

                navigationDropdown.Items.Add(item);
            }
            foreach (LuaTable table in m_doc.tables)
            {
                if (table.m_sName != "<unnamed table>")
                {
                    ComboBoxItem item = new ComboBoxItem();
                    item.Content = table.m_sName;
                    item.ToolTip = table.m_values.ToString();

                    navigationDropdown.Items.Add(item);
                }
            }
                
        }

        private void navListSelctedItemChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_doc.functions != null)
            {
                foreach (CodeTable.LuaFunction func in m_doc.functions)
                {
                    ComboBoxItem item = navigationDropdown.SelectedItem as ComboBoxItem;

                    if (item == null)
                        break;

                    if (func.name == (string)(item.Content))
                    {
                        textEditor.ScrollToLine(textEditor.Document.GetLineByOffset(func.m_codeBlock.m_startAt).LineNumber);
                        textEditor.SelectionStart = func.m_codeBlock.m_startAt;
                        textEditor.Focus();
                        break;
                    }
 
                }

                foreach (LuaTable table in m_doc.tables)
                {
                    ComboBoxItem item = navigationDropdown.SelectedItem as ComboBoxItem;

                    if (item == null)
                        break;

                    if (table.m_sName == (string)(item.Content))
                    {
                        textEditor.ScrollToLine(textEditor.Document.GetLineByOffset(table.m_codeBlock.m_startAt).LineNumber);
                        textEditor.SelectionStart = table.m_codeBlock.m_startAt;
                        textEditor.Focus();
                        break;
                    }
                }
            }
        }

      
    }

    
}
